package blackstar.performance.logger;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import blackstar.performance.DaemonThreadFactory;
import blackstar.performance.LogStat;

/**
 * @author RayWoo (ayufox@gmail.com)
 * 
 */
public final class RollingFileLogger
{
	private final static Logger LOGGER = Logger
			.getLogger(RollingFileLogger.class.getSimpleName());

	public final static int ROLLING_TYPE_DAY = 1;
	public final static int ROLLING_TYPE_HOUR = 2;

	private final static String DIR_PATTERN = "yyyyMMdd";
	private final static String HOUR_PATTERN = "HH";
	public final static int DEFAULT_BUFFER_SIZE = 1024 * 1024; // 1M
	private String dirName;
	private String fileName;
	private BufferWriter writer;

	private int rollingType = ROLLING_TYPE_HOUR;

	private ScheduledExecutorService scheduledExecutorService;
	private ScheduledFuture scheduledFuture;
	private boolean createScheduler = false;

	public RollingFileLogger(String dirName, String fileName)
	{
		this(dirName, fileName, DEFAULT_BUFFER_SIZE, ROLLING_TYPE_HOUR);
	}

	public RollingFileLogger(String dirName, String fileName, int rollingType)
	{
		this(dirName, fileName, DEFAULT_BUFFER_SIZE, rollingType);
	}

	public RollingFileLogger(String dirName, String fileName, int bufferLength,
			int rollingType)
	{
		this(dirName, fileName, bufferLength, rollingType, null);
	}

	public RollingFileLogger(String dirName, String fileName, int bufferLength,
			int rollingType, ScheduledExecutorService scheduledExecutorService)
	{
		this.dirName = dirName;
		this.fileName = fileName;
		this.rollingType = rollingType;
		this.scheduledExecutorService = scheduledExecutorService;
		if (this.scheduledExecutorService == null)
		{
			this.scheduledExecutorService = Executors.newScheduledThreadPool(1,
					new DaemonThreadFactory());
			this.createScheduler = true;
		}
		this.writer = new BufferWriter(bufferLength, this.scheduledExecutorService);

		scheduleRollover();
	}
	
	public void setLogStat(LogStat logStat)
	{
		this.writer.setLogStat(logStat);
	}

	public void scheduleRollover()
	{
		try
		{
			this.rollOver();
		} catch (IOException e)
		{
			LOGGER.warning("error rollOver:" + e.getMessage());
		}

		Calendar calendar = Calendar.getInstance();
		switch (this.rollingType)
		{
		case ROLLING_TYPE_DAY:
			calendar.set(Calendar.HOUR_OF_DAY, 0);
			calendar.set(Calendar.MINUTE, 0);
			calendar.set(Calendar.SECOND, 0);
			calendar.set(Calendar.MILLISECOND, 0);
			calendar.add(Calendar.DATE, 1);
			break;
		case ROLLING_TYPE_HOUR:
			calendar.set(Calendar.MINUTE, 0);
			calendar.set(Calendar.SECOND, 0);
			calendar.set(Calendar.MILLISECOND, 0);
			calendar.add(Calendar.HOUR_OF_DAY, 1);
			break;
		default:
			throw new IllegalStateException("Unknown rolling type.");
		}

		this.scheduledFuture = this.scheduledExecutorService.schedule(
				new Runnable()
				{
					public void run()
					{
						scheduleRollover();
					}
				}, (calendar.getTimeInMillis() - System.currentTimeMillis()),
				TimeUnit.MILLISECONDS);
	}

	public void stop()
	{
		if (this.createScheduler)
		{
			if (this.scheduledFuture != null)
			{
				try
				{
					this.scheduledFuture.cancel(true);
				} catch (Exception e)
				{
					LOGGER.warning("cancel scheduledFuture failed!");
				}
			}

			this.scheduledExecutorService.shutdownNow();
		}
	}

	public void log(StringBuilder message)
	{
		this.writer.write(message);
	}

	protected void rollOver() throws IOException
	{
		Date now = new Date();
		String datedFilename = dirName + File.separator
				+ new SimpleDateFormat(DIR_PATTERN).format(now)
				+ File.separator + fileName;
		switch (this.rollingType)
		{
		case ROLLING_TYPE_DAY:
			break;
		case ROLLING_TYPE_HOUR:
			datedFilename = datedFilename
					+ new SimpleDateFormat(HOUR_PATTERN).format(now);
			break;
		default:
			throw new IllegalStateException("Unknown rolling type.");
		}
		datedFilename = datedFilename + ".log";

		try
		{
			this.writer.resetFile(new File(datedFilename));
		} catch (IOException e)
		{
			LOGGER.warning("setFile(" + fileName + ", true) call failed.");
		}
	}

	public void close()
	{
		try
		{
			LOGGER.info("Close Writer");
			this.writer.close();
		} catch (IOException e)
		{
			LOGGER.warning("Error When Close RollingFileLogger : "
					+ e.getMessage());
		}
	}
}
